package cn.com.xiaoshoujia.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.DESedeKeySpec;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.SecureRandom;

/**
 * <p>Title:加密解密测试</p>
 * <p>
 * <p>Description: 加密解密</p>
 */
public class Endecrypt {


    static Logger logger = LoggerFactory.getLogger(Endecrypt.class);

    public static void main(String[] args) throws Exception {
        String userid = "12dsfzxcv~~!23123zxcvsdf3";
        String key = "0eb89523f952497f9afb0c45280c9318";

        byte[] result = getDESEncrypt(userid, key);
        System.out.println("加密后：" + getBase64Encode(result));


        byte[] decryResult = getDESDecrypt(result, key);
        System.out.println("解密后：" + new String(decryResult));


    }

    public static byte[] getDESEncrypt(String data, String key) {
        if (data == null)
            return null;
        byte[] res = null;
        try {
            res = getDESEncryptORdecrypt(data.getBytes("utf-8"), key.getBytes("utf-8"), Cipher.ENCRYPT_MODE);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    public static byte[] getDESDecrypt(byte[] data, String key) {
        if (data == null)
            return null;
        byte[] res = null;
        try {
            res = getDESEncryptORdecrypt(data, key.getBytes("utf-8"), Cipher.DECRYPT_MODE);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * Description 加解密
     *
     * @param data
     * @param key
     * @return
     * @throws Exception
     */
    private static byte[] getDESEncryptORdecrypt(byte[] data, byte[] key, final int cipherMode) throws Exception {
        // 生成一个可信任的随机数源
        SecureRandom sr = new SecureRandom();
        // 从原始密钥数据创建DESKeySpec对象
        DESKeySpec dks = new DESKeySpec(key);
        // 创建一个密钥工厂，然后用它把DESKeySpec转换成SecretKey对象
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey securekey = keyFactory.generateSecret(dks);
        // Cipher对象实际完成解密操作
        Cipher cipher = Cipher.getInstance("DES");
        // 用密钥初始化Cipher对象
        cipher.init(cipherMode, securekey, sr);
        return cipher.doFinal(data);
    }


    /**
     * 进行MD5加密
     *
     * @param strSrc 原始的SPKEY
     * @return byte[] 指定加密方式为md5后的byte[]
     */

    private static byte[] md5(String strSrc) {
        byte[] returnByte = null;
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            returnByte = md5.digest(strSrc.getBytes("GBK"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return returnByte;
    }


    /**
     * 得到3-DES的密钥匙
     * 根据接口规范，密钥匙为24个字节，md5加密出来的是16个字节，因此后面补8个字节的0
     *
     * @param spKey 原始的SPKEY
     * @return byte[] 指定加密方式为md5后的byte[]
     */

    private static byte[] getEnKey(String spKey) {
        byte[] desKey = null;
        try {
            byte[] desKey1 = md5(spKey);
            desKey = new byte[24];
            int i = 0;
            while (i < desKey1.length && i < 24) {
                desKey[i] = desKey1[i];
                i++;
            }
            if (i < 24) {
                desKey[i] = 0;
                i++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return desKey;
    }


    /**
     * 3-DES加密
     *
     * @param src   要进行3-DES加密的byte[]
     * @param enKey 3-DES加密密钥
     * @return byte[] 3-DES加密后的byte[]
     */

    public static byte[] Encrypt(byte[] src, byte[] enKey) {
        byte[] encryptedData = null;
        try {
            DESedeKeySpec dks = new DESedeKeySpec(enKey);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
            SecretKey key = keyFactory.generateSecret(dks);
            Cipher cipher = Cipher.getInstance("DESede");
            cipher.init(Cipher.ENCRYPT_MODE, key);
            encryptedData = cipher.doFinal(src);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encryptedData;
    }


    /**
     * 对字符串进行Base64编码
     *
     * @param src 要进行编码的字符
     * @return String 进行编码后的字符串
     */

    public static String getBase64Encode(byte[] src) {
        String requestValue = "";
        try {
            BASE64Encoder base64en = new BASE64Encoder();
            requestValue = base64en.encode(src);
            //System.out.println(requestValue);
        } catch (Exception e) {
            logger.debug("Base64编码 失败");
        }

        return requestValue;
    }


    /**
     * 去掉字符串的换行符号
     * base64编码3-DES的数据时，得到的字符串有换行符号
     * ，一定要去掉，否则uni-wise平台解析票根不会成功，
     * 提示“sp验证失败”。在开发的过程中，因为这个问题让我束手无策，
     * 一个朋友告诉我可以问联通要一段加密后 的文字，然后去和自己生成的字符串比较，
     * 这是个不错的调试方法。我最后比较发现我生成的字符串唯一不同的 是多了换行。
     * 我用c#语言也写了票根请求程序，没有发现这个问题。
     */

    private static String filter(String str) {
        String output = null;
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < str.length(); i++) {
            int asc = str.charAt(i);
            if (asc != 10 && asc != 13)
                sb.append(str.subSequence(i, i + 1));
        }
        output = new String(sb);
        return output;
    }


    /**
     * 对字符串进行URLDecoder.encode(strEncoding)编码
     *
     * @param src 要进行编码的字符串
     * @return String 进行编码后的字符串
     */

    public static String getURLEncode(String src) {
        String requestValue = "";
        try {

            requestValue = URLEncoder.encode(src, "utf-8");
        } catch (Exception e) {
            logger.debug("URLDecoder编码 失败");
        }

        return requestValue;
    }

    /**
     * 3-DES加密
     *
     * @param src   要进行3-DES加密的String
     * @param spkey 分配的SPKEY
     * @return String 3-DES加密后的String
     */

    public static String get3DESEncrypt(String src, String spkey) {
        String requestValue = "";
        try {


            //得到3-DES的密钥匙
            byte[] enKey = getEnKey(spkey);
            //要进行3-DES加密的内容在进行/"UTF-16LE/"取字节
            byte[] src2 = src.getBytes("UTF-16LE");
            //进行3-DES加密后的内容的字节
            byte[] encryptedData = Encrypt(src2, enKey);


            //进行3-DES加密后的内容进行BASE64编码
            String base64String = getBase64Encode(encryptedData);
            //BASE64编码去除换行符后
            String base64Encrypt = filter(base64String);

            //对BASE64编码中的HTML控制码进行转义的过程
            requestValue = getURLEncode(base64Encrypt);
            //System.out.println(requestValue);
        } catch (Exception e) {
            logger.debug("3-DES加密 失败");
        }

        return requestValue;
    }


    /**
     * 对字符串进行URLDecoder.decode(strEncoding)解码
     *
     * @param src 要进行解码的字符串
     * @return String 进行解码后的字符串
     */

    public static String getURLDecoderdecode(String src) {
        String requestValue = "";
        try {

            requestValue = URLDecoder.decode(src);
        } catch (Exception e) {
            logger.debug("URLDecoder解码 失败");
        }

        return requestValue;
    }


    /**
     * 进行3-DES解密（密钥匙等同于加密的密钥匙）。
     *
     * @param debase64 要进行3-DES解密byte[]
     * @param spKey    分配的SPKEY
     * @return String 3-DES解密后的String
     */
    public static String deCrypt(byte[] debase64, String spKey) {
        String strDe = null;
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("DESede");
            byte[] key = getEnKey(spKey);
            DESedeKeySpec dks = new DESedeKeySpec(key);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
            SecretKey sKey = keyFactory.generateSecret(dks);
            cipher.init(Cipher.DECRYPT_MODE, sKey);
            byte ciphertext[] = cipher.doFinal(debase64);
            strDe = new String(ciphertext, "UTF-16LE");
        } catch (Exception ex) {
            strDe = "";
            logger.debug("3-DES解密 失败");
        }
        return strDe;
    }


    /**
     * 3-DES解密
     *
     * @param src   要进行3-DES解密的String
     * @param spkey 分配的SPKEY
     * @return String 3-DES加密后的String
     */

    public static String get3DESDecrypt(String src, String spkey) {
        String requestValue = "";
        try {


            //得到3-DES的密钥匙

            //URLDecoder.decodeTML控制码进行转义的过程
            String URLValue = getURLDecoderdecode(src);

            //进行3-DES加密后的内容进行BASE64编码

            BASE64Decoder base64Decode = new BASE64Decoder();
            byte[] base64DValue = base64Decode.decodeBuffer(URLValue);

            //要进行3-DES加密的内容在进行/"UTF-16LE/"取字节

            requestValue = deCrypt(base64DValue, spkey);
        } catch (Exception e) {
            logger.debug("3-DES解密 失败");
        }
        return requestValue;
    }

    public static String makeCode(String str, String key, String flag) {
        String[] s = shortText(str, key + "&" + flag);
        if (s != null)
            return s[0];
        return "";
    }

    public static String[] shortText(String string, String key) {
        //自定义生成MD5加密字符串前的混合KEY
//        String key = "XuLiang";
        String[] chars = new String[]{          //要使用生成URL的字符
                "a", "b", "c", "d", "e", "f", "g", "h",
                "i", "j", "k", "l", "m", "n", "o", "p",
                "q", "r", "s", "t", "u", "v", "w", "x",
                "y", "z", "0", "1", "2", "3", "4", "5",
                "6", "7", "8", "9", "A", "B", "C", "D",
                "E", "F", "G", "H", "I", "J", "K", "L",
                "M", "N", "O", "P", "Q", "R", "S", "T",
                "U", "V", "W", "X", "Y", "Z"
        };

        String hex = MD5Tools.getMD5(key + string);
        int hexLen = hex.length();
        int subHexLen = hexLen / 8;
        String[] ShortStr = new String[4];

        for (int i = 0; i < subHexLen; i++) {
            String outChars = "";
            int j = i + 1;
            String subHex = hex.substring(i * 8, j * 8);
            long idx = Long.valueOf("3FFFFFFF", 16) & Long.valueOf(subHex, 16);

            for (int k = 0; k < 6; k++) {
                int index = (int) (Long.valueOf("0000003D", 16) & idx);
                outChars += chars[index];
                idx = idx >> 5;
            }
            ShortStr[i] = outChars;
        }

        return ShortStr;
    }
}
